/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.commonmodel.engine.core.data.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.bef.bizentity.common.BefDtBeanUtil;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.api.manager.serialize.CefSerializeContext;
import com.inspur.edp.cef.api.manager.serialize.JsonFormatType;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementDataType;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.spi.entity.info.propertyinfo.DataTypePropertyInfo;
import com.inspur.edp.cef.spi.jsonser.base.StringUtils;
import com.inspur.edp.cef.spi.jsonser.entity.AbstractEntitySerializerItem;
import com.inspur.edp.cef.spi.jsonser.util.SerializerUtil;
import com.inspur.edp.commonmodel.engine.api.data.AssociationInfo;
import com.inspur.edp.commonmodel.engine.core.common.CMUtil;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.metadata.rtcustomization.api.CustomizationRtService;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
import lombok.var;

public class EngineSerializerItem extends AbstractEntitySerializerItem {

  protected IGspCommonObject node;

  public EngineSerializerItem(IGspCommonObject node) {
    this();
    this.node = node;
  }

  public EngineSerializerItem() {
  }

  private Object readAssociation(
      GspAssociation asso, JsonParser reader, DeserializationContext serializer) {
    if (reader.getCurrentToken() == JsonToken.VALUE_NULL) {
      return null;
    }
    AssociationInfo info = createAssociationInfo(asso);
    try {
      reader.nextToken();
      while (reader.getCurrentToken() == JsonToken.FIELD_NAME) {
        String propertyName = reader.getValueAsString();
        reader.nextToken();
        if (asso.getBelongElement().getLabelID().equalsIgnoreCase(propertyName)) {
          info.setValue(asso.getBelongElement().getLabelID(), readString(reader));
        } else {
          IGspCommonElement element = CMUtil.checkRefElementExists(asso, propertyName);
          info.setValue(element.getLabelID(), readElementInfo(element, reader, serializer));
        }
        reader.nextToken();
      }
//      reader.nextToken();
      return info;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  protected AssociationInfo createAssociationInfo(GspAssociation association) {
    return new AssociationInfo(association);
  }

  private void writeAssociation(
      GspAssociation asso,
      String propName,
      AssociationInfo assoValue,
      JsonGenerator writer,
      SerializerProvider serializer) {
    try {
      writer.writeFieldName(StringUtils.toCamelCase(propName));
      if (assoValue == null) {
        writer.writeNull();
      } else {
        writer.writeStartObject();
        writeString(writer, assoValue.getValue(asso.getBelongElement().getLabelID()),
            asso.getBelongElement().getLabelID(), serializer);
        for (IGspCommonField refElement : asso.getRefElementCollection()) {
          writeElementInfo(
              refElement, assoValue.getValue(refElement.getLabelID()), writer, serializer);
        }
        writer.writeEndObject();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void writeEntityBasicInfo(JsonGenerator writer, IEntityData data,
      SerializerProvider serializer) {
    getElements().forEach(
        element -> {
          Object value = data.getValue(element.getLabelID());
          if (value instanceof Date) {
            if (element.getMDataType() == GspElementDataType.Date)
              this.writeDate(writer, value, element.getLabelID(), serializer);
            else
              this.writeDateTime(writer, value, element.getLabelID(), serializer);
          } else if(element.getObjectType() == GspElementObjectType.Association){
            if(getCefSerializeContext().getJsonFormatType() != JsonFormatType.Tiled)
              this.writeAssociation(writer, value, element.getLabelID(), serializer);
            else
              writeTiledAssociation(writer, value, element, serializer,"");
          }else{
            this.writeBaseType(writer, value, element.getLabelID(), serializer);
          }
        });
  }

  private void writeTiledAssociation(JsonGenerator writer, Object value, IGspCommonField element, SerializerProvider serializer, String prefix){

    AssociationInfo info = (AssociationInfo)value;
    writeString(writer, info.getValue(), prefix + element.getLabelID(), serializer);
    GspAssociation association = element.getChildAssociations().get(0);
    for(IGspCommonField refEle : association.getRefElementCollection()){
      if(refEle.getObjectType() != GspElementObjectType.Association){
        switch (refEle.getMDataType()) {
          case Date:
            writeDate(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
            break;
          case DateTime:
            writeDateTime(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
            break;
          default:
            writeBaseType(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
        }
      }
      else{
        GspMetadata metadata = MetadataUtil.getCustomRTMetadata(refEle.getParentAssociation().getRefModelID());
        GspBusinessEntity be = (GspBusinessEntity) metadata.getContent();
        GspBizEntityObject obj = be
                .getNode(element.getParentAssociation().getRefObjectCode());

        IGspCommonField belongField = null;
        for (IGspCommonField gspCommonField : obj.getContainElements()) {
          if (gspCommonField.getID().equals(element.getRefElementId())) {
            belongField = gspCommonField;
            break;
          }
        }
        writeTiledAssociation(writer, info.getValue(refEle.getLabelID()), belongField, serializer, prefix+refEle.getLabelID()+"_");

      }
    }

  }
  private void writeElementInfo(
      IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
    switch (element.getObjectType()) {
      case None:
        writeNormalElementInfo(element, value, writer, serializer);
        break;
      case Association:
        writeAssociationElementInfo(element, value, writer, serializer);
        break;
      case Enum:
        writeEnumElementInfo(element, value, writer, serializer);
        break;
      default:
        throw new RuntimeException(
            "不支持序列化此字段类型" + element.getLabelID() + "." + element.getObjectType());
    }
  }

  private void writeEnumElementInfo(
      IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
    writeString(writer, value, element.getLabelID(), serializer);
  }

  private void writeAssociationElementInfo(
      IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
    // writeAssociation(writer, value, element.getLabelID(), serializer);
    GspAssociation asso = element.getChildAssociations().get(0);
    writeAssociation(asso, element.getLabelID(), (AssociationInfo) value, writer, serializer);
  }

  private void writeNormalElementInfo(
      IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
    switch (element.getMDataType()) {
      case String:
      case Text:
        writeString(writer, value, element.getLabelID(), serializer);
        break;
      case Integer:
        writeInt(writer, value, element.getLabelID(), serializer);
        break;
      case Decimal:
        writeDecimal(writer, value, element.getLabelID(), serializer);
        break;
      case Boolean:
        writeBool(writer, value, element.getLabelID(), serializer);
        break;
      case Date:
        writeDate(writer, value, element.getLabelID(), serializer);
        break;
      case DateTime:
        writeDateTime(writer, value, element.getLabelID(), serializer);
        break;
      case Binary:
        writeBytes(writer, value, element.getLabelID(), serializer);
        break;
      default:
        throw new RuntimeException(
            "无法识别的MDataType" + element.getLabelID() + "." + element.getMDataType());
    }
  }

  @Override
  public boolean readEntityBasicInfo(
      JsonParser reader, DeserializationContext serializer, IEntityData data,
      String propertyName) {
    IGspCommonField element = getElements()
        .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName))
        .findFirst().orElse(null);
    if (element == null) {
      return false;
    }
    data.setValue(element.getLabelID(), readElementInfo(element, reader, serializer));
    return true;
  }

  private Object readElementInfo(
      IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
    if (element.getIsUdt()) {
      //todo 解析型udt平铺格式暂不支持
      return readUdtElementInfo(element, reader, serializer);
    }

    switch (element.getObjectType()) {
      case None:
        return readNormalElementInfo(element, reader, serializer);
      case Association:
        return readAssociationElementInfo(element, reader, serializer);
      case Enum:
        return readEnumElementInfo(element, reader, serializer);
      default:
        throw new RuntimeException(
            "不支持序列化此字段类型" + element.getLabelID() + "." + element.getObjectType());
    }
  }

  private Object readUdtElementInfo(IGspCommonField element, JsonParser reader,
      DeserializationContext serializer) {
    var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
    return readNestedValue(udtConfigId, reader, serializer);
  }

  private Object readEnumElementInfo(
      IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
    return readString(reader);
  }

  private Object readAssociationElementInfo(
      IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
    GspAssociation gspAssociation = null;
    if (element.getIsRefElement()) {
      GspMetadata metadata = MetadataUtil.getCustomRTMetadata(element.getParentAssociation().getRefModelID());
      GspBusinessEntity gspBusinessEntity = (GspBusinessEntity) metadata.getContent();
      GspBizEntityObject gspBizEntityObject = gspBusinessEntity
          .getNode(element.getParentAssociation().getRefObjectCode());
      for (IGspCommonField gspCommonField : gspBizEntityObject.getContainElements()) {
        if (gspCommonField.getID().equals(element.getRefElementId())) {
          gspAssociation = gspCommonField.getChildAssociations().get(0);
          break;
        }
      }
    } else {
      gspAssociation = element.getChildAssociations().get(0);
    }
    return readAssociation(gspAssociation, reader, serializer);
  }

  private Object readNormalElementInfo(
      IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
    switch (element.getMDataType()) {
      case String:
      case Text:
        return readString(reader);
      case Integer:
        return readInt(reader);
      case Decimal:
        return readDecimal(reader);
      case Boolean:
        return readBool(reader);
      case Date:
      case DateTime:
        return readDateTime(reader);
      case Binary:
        return readBytes(reader);
      default:
        throw new RuntimeException(
            "无法识别的MDataType" + element.getLabelID() + "." + element.getMDataType());
    }
  }

  @Override
  public boolean writeModifyPropertyJson(
      JsonGenerator writer, String propertyName, Object value, SerializerProvider serializer) {
    //变更集的序列化, 0227与大家讨论后一致决定, 在序列化基类中对于seritem不处理的属性执行默认序列化, 所以此处
    //可以直接return false交由序列化基类处理.
    return false;
  }

  @Override
  public Object readModifyPropertyValue(JsonParser reader, DeserializationContext serializer,
      HashMap<String, Object> changeValues,
      RefObject<String> propertyName,
      RefObject<Boolean> hasRead,
      CefSerializeContext context){
    //todo 临时处理，仅处理最简单场景。后续引入ResInfo后，基类可修改为CefEntityDataSerializerItem，此方法就不需要复写了，可以直接删除
    IGspCommonElement element = null;
    for (IGspCommonElement element1 : this.node.getAllElementList(true)) {
      if (element1.getLabelID().equalsIgnoreCase((String)propertyName.argvalue)) {
        element = element1;
        break;
      }
    }
    if (element == null) {
      hasRead.argvalue = false;
      return null;
    }
    hasRead.argvalue = true;
    propertyName.argvalue = element.getLabelID();
    if (element.getIsRefElement()) {
      IGspCommonField belongEle = element.getParentAssociation().getBelongElement();
      Object assoInfo = changeValues.get(belongEle.getLabelID());
      if (assoInfo == null)
        return null;
      Object value = readModifyByElement(reader, serializer, element, context, changeValues);
      ((AssociationInfo)assoInfo).setValue(element.getLabelID(), value);
    }

    return readModifyByElement(reader, serializer, element, context, changeValues);

  }

  private Object readModifyByElement(JsonParser reader, DeserializationContext serializer, IGspCommonField element, CefSerializeContext context, HashMap<String, Object> changeValues) {
    if (element.getIsUdt()) {

      String udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
      return readNestedChange(udtConfigId, reader, serializer);
    }
    switch (element.getObjectType()) {
      case None:
        return readNormalElement(reader, serializer, element);
      case Association:
        return readAssociation(reader, serializer, element, context, changeValues);
      case Enum:
        return readEnumElement(reader, serializer, element);
      case DynamicProp:
        throw new RuntimeException("暂不支持动态属性类型的变更集，当前字段标签为：" + element.getLabelID());
    }
    throw new RuntimeException(String.format("当前类型为%1$s的字段暂不支持进行变更数据，当前字段标签为：%2$s" , element.getObjectType(),element.getLabelID()));
  }

  private Object readAssociation(JsonParser reader, DeserializationContext serializer, IGspCommonField element, CefSerializeContext context, HashMap<String, Object> changeValues) {
    if (context.getJsonFormatType() == JsonFormatType.Tiled)
      return readTiledAssociation(reader, serializer, element, changeValues);
    return readAssociation(reader, serializer, element);
  }


  private Object readTiledAssociation(JsonParser reader, DeserializationContext serializer, IGspCommonField element, HashMap<String, Object> changeValues) {
    Objects.requireNonNull(element.getChildAssociations().get(0), element
        .getLabelID() + ".getChildAssociations().get(0)");
    AssociationInfo associationInfo = new AssociationInfo((GspAssociation)element.getChildAssociations().get(0));
    Object value = SerializerUtil.readString(reader);
    associationInfo.setValue(element.getLabelID(), value);
    changeValues.put(element.getLabelID(), associationInfo);
    return associationInfo;
  }
  @Override
  public Object readModifyPropertyValue(
      JsonParser reader,
      DeserializationContext serializer,
      RefObject<String> propertyName,
      RefObject<Boolean> hasRead) {
    var element = getElements()
        .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName.argvalue)).findFirst();
    if (!element.isPresent()) {
      hasRead.argvalue = false;
      return null;
    }
    hasRead.argvalue = true;
    propertyName.argvalue = element.get().getLabelID();
    return readModifyByElement(reader, serializer, element.get());
  }

  private Stream<IGspCommonField> getElements() {
    Stream rez = node.getContainElements().stream();
    if (getElementPredicate() != null) {
      rez = rez.filter(getElementPredicate());
    }
    return rez;
  }

  private Stream<IGspCommonField> getElements(boolean containRef) {
    Stream rez = node.getAllElementList(containRef).stream();
    if (getElementPredicate() != null) {
      rez = rez.filter(getElementPredicate());
    }
    return rez;
  }

  private Object readModifyByElement(JsonParser reader,
      DeserializationContext serializer, IGspCommonField element) {
    if (element.getIsUdt()) {
      //todo 暂不支持平铺格式udt反序列化
      var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
      return readNestedChange(udtConfigId, reader, serializer);
    }
    switch (element.getObjectType()) {
      case None:
        return readNormalElement(reader, serializer, element);
      case Association:
        return readAssociation(reader, serializer, element);
      case Enum:
        return readEnumElement(reader, serializer, element);
      default:
        throw new RuntimeException("暂不支持的字段类型:" + element.getLabelID());
    }
  }

  private Object readEnumElement(JsonParser reader, DeserializationContext serializer,
      IGspCommonField element) {
    return readString(reader);
  }

  private Object readNormalElement(JsonParser reader, DeserializationContext serializer,
      IGspCommonField element) {
    switch (element.getMDataType()) {
      case String:
      case Text:
        return readString(reader);
      case Integer:
        return readInt(reader);
      case Decimal:
        return readDecimal(reader);
      case Boolean:
        return readBool(reader);
      case Date:
      case DateTime:
        return readDateTime(reader);
      case Binary:
        return readBytes(reader);
      default:
        throw new RuntimeException(
            "无法识别的MDataType" + element.getLabelID() + "." + element.getMDataType());
    }
  }

  private AssociationInfo readAssociation(JsonParser reader, DeserializationContext serializer,
      IGspCommonField element) {
    Objects.requireNonNull(element.getChildAssociations().get(0),
        element.getLabelID() + ".getChildAssociations().get(0)");

    if (reader.getCurrentToken() == JsonToken.VALUE_NULL) {
      return null;
    }
    if (reader.getCurrentToken() != JsonToken.START_OBJECT) {
      throw new RuntimeException("Association must start with ObjectToken");
    }
    try {
      reader.nextToken();
      AssociationInfo rez = new AssociationInfo(element.getChildAssociations().get(0));

      while (reader.getCurrentToken() != JsonToken.END_OBJECT) {
        String propertyName = reader.getValueAsString();
        reader.nextToken();

        Object value;
        String actualName;
        if(element.getLabelID().equalsIgnoreCase(propertyName)){
          value = readString(reader);
          actualName = element.getLabelID();
        }else {
          IGspCommonField refEle = element.getChildAssociations().get(0)
              .getRefElementCollection().stream()
              .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName)).findFirst()
              .orElse(null);
          if (refEle == null) {
            throw new RuntimeException("带出字段不存在" + element.getLabelID() + "." + propertyName);
          }
          value =  readAssociationRefElement(reader, serializer, refEle);
          actualName = refEle.getLabelID();
        }
        rez.setValue(actualName, value);
        reader.nextToken();
      }
      return rez;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private Object readAssociationRefElement(JsonParser reader,
      DeserializationContext serializer, IGspCommonField element) {
    if (element.getIsUdt()) {
      var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
      return readNestedValue(udtConfigId, reader, serializer);
    }
    switch (element.getObjectType()) {
      case None:
        return readNormalElement(reader, serializer, element);
      case Association:
         GspAssociation parentAssociation = element.getParentAssociation();
         GspMetadata refMetadata = MetadataUtil.getCustomRTMetadata(parentAssociation.getRefModelID());
         Objects.requireNonNull(refMetadata, parentAssociation.getBelongElement().getLabelID()+"的关联元数据:" + parentAssociation.getRefModelID());
         Objects.requireNonNull(refMetadata.getContent(), parentAssociation.getBelongElement().getLabelID()+"的关联元数据:" + parentAssociation.getRefModelID());
         GspBusinessEntity refBe = (GspBusinessEntity) refMetadata.getContent();
         GspBizEntityObject refNode =
             org.springframework.util.StringUtils.isEmpty(parentAssociation.getRefObjectCode())
                 ? refBe.getMainObject() : refBe.getNode(parentAssociation.getRefObjectCode());
         IGspCommonElement referedElement = refNode.findElement(element.getRefElementId());
         Objects.requireNonNull(referedElement, element.getName()+"关联的字段不存在");
         return readAssociation(reader, serializer, referedElement);
      case Enum:
        return readEnumElement(reader, serializer, element);
      default:
        throw new RuntimeException("暂不支持的字段类型:" + element.getLabelID());
    }
  }

  protected Predicate<IGspCommonField> getElementPredicate() {
    return null;
  }
}
